<?php
/**
 * 短信验证码服务 -- easySms
 * zhouliang
 * 2020.08.29
 */

namespace App\Services\Tools;

use Carbon\Carbon;
use Exception;
use Overtrue\EasySms\EasySms;
use App\Exceptions\BasicException;
use Illuminate\Support\Facades\Log;

class NeteaseSmsService {
    public static $AppKey;                //开发者平台分配的AppKey
    public static $AppSecret;             //开发者平台分配的AppSecret,可刷新
    public static $Nonce;                    //随机数（最大长度128个字符）
    public static $CurTime;                //当前UTC时间戳，从1970年1月1日0点0 分0 秒开始到现在的秒数(String)
    public static $CheckSum;                //SHA1(AppSecret + Nonce + CurTime),三个参数拼接的字符串，进行SHA1哈希计算，转化成16进制字符(String，小写)
    public static $RequestType;
    private static $_error_msg  = '';

    const HEX_DIGITS = "0123456789abcdef";

    /**
     * 参数初始化
     * @param $AppKey
     * @param $AppSecret
     * @param $RequestType [选择php请求方式，fsockopen或curl,若为curl方式，请检查php配置是否开启]
     */
    public function __construct($RequestType = 'curl')
    {
        $config = self::getConfig();
        self::$AppKey = $config['AppKey'];
        self::$AppSecret = $config['AppSecret'];
        self::$RequestType = $RequestType;
    }

    /**
     * API checksum校验生成
     * @param void
     * @return $CheckSum(对象私有属性)
     */
    public static function checkSumBuilder()
    {
        //此部分生成随机字符串
        $hex_digits = self::HEX_DIGITS;
        self::$Nonce;
        for ($i = 0; $i < 128; $i++) {            //随机字符串最大128个字符，也可以小于该数
            self::$Nonce .= $hex_digits[rand(0, 15)];
        }
        self::$CurTime = (string)(time());    //当前时间戳，以秒为单位

        $join_string = self::$AppSecret . self::$Nonce . self::$CurTime;
        self::$CheckSum = sha1($join_string);
        //print_r($this->CheckSum);
    }

    /**
     * 将json字符串转化成php数组
     * @param  $json_str
     * @return $json_arr
     */
    public static function json_to_array($json_str)
    {

        if (is_array($json_str) || is_object($json_str)) {
            $json_str = $json_str;
        } else if (is_null(json_decode($json_str))) {
            $json_str = $json_str;
        } else {
            $json_str = strval($json_str);
            $json_str = json_decode($json_str, true);
        }
        $json_arr = array();
        foreach ($json_str as $k => $w) {
            if (is_object($w)) {
                $json_arr[$k] = self::json_to_array($w); //判断类型是不是object
            } else if (is_array($w)) {
                $json_arr[$k] = self::json_to_array($w);
            } else {
                $json_arr[$k] = $w;
            }
        }
        return $json_arr;
    }

    /**
     * 使用CURL方式发送post请求
     * @param  $url [请求地址]
     * @param  $data [array格式数据]
     * @return $请求返回结果(array)
     */
    public static function postDataCurl($url, $data)
    {
        self::checkSumBuilder();       //发送请求前需先生成checkSum

        $timeout = 5000;
        $http_header = array(
            'AppKey:' . self::$AppKey,
            'Nonce:' . self::$Nonce,
            'CurTime:' . self::$CurTime,
            'CheckSum:' . self::$CheckSum,
            'Content-Type:application/x-www-form-urlencoded;charset=utf-8'
        );
        //print_r($http_header);

        // $postdata = '';
        $postdataArray = array();
        foreach ($data as $key => $value) {
            array_push($postdataArray, $key . '=' . urlencode($value));
        }
        $postdata = join('&', $postdataArray);

        // var_dump($postdata);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $http_header);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //处理http证书问题
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $result = curl_exec($ch);
        if (false === $result) {
            $result = curl_errno($ch);
        }
        curl_close($ch);
        return self::json_to_array($result);
    }


    /**
     * 使用FSOCKOPEN方式发送post请求
     * @param  $url [请求地址]
     * @param  $data [array格式数据]
     * @return $请求返回结果(array)
     */
    public function postDataFsockopen($url, $data)
    {
        $this->checkSumBuilder();//发送请求前需先生成checkSum

        $postdata = '';
        foreach ($data as $key => $value) {
            $postdata .= ($key . '=' . urlencode($value) . '&');
        }
        // building POST-request:
        $URL_Info = parse_url($url);
        if (!isset($URL_Info["port"])) {
            $URL_Info["port"] = 80;
        }
        $request = '';
        $request .= "POST " . $URL_Info["path"] . " HTTP/1.1\r\n";
        $request .= "Host:" . $URL_Info["host"] . "\r\n";
        $request .= "Content-type: application/x-www-form-urlencoded;charset=utf-8\r\n";
        $request .= "Content-length: " . strlen($postdata) . "\r\n";
        $request .= "Connection: close\r\n";
        $request .= "AppKey: " . $this->AppKey . "\r\n";
        $request .= "Nonce: " . $this->Nonce . "\r\n";
        $request .= "CurTime: " . $this->CurTime . "\r\n";
        $request .= "CheckSum: " . $this->CheckSum . "\r\n";
        $request .= "\r\n";
        $request .= $postdata . "\r\n";

        print_r($request);
        $fp = fsockopen($URL_Info["host"], $URL_Info["port"]);
        fputs($fp, $request);
        $result = '';
        while (!feof($fp)) {
            $result .= fgets($fp, 128);
        }
        fclose($fp);

        $str_s = strpos($result, '{');
        $str_e = strrpos($result, '}');
        $str = substr($result, $str_s, $str_e - $str_s + 1);
        print_r($result);
        return self::json_to_array($str);
    }

    /**
     * 发送短信验证码
     * @param  $templateid [模板编号(由客服配置之后告知开发者)]
     * @param  $mobile [目标手机号]
     * @param  $deviceId [目标设备号，可选参数]
     * @return $authCode
     */
    public static function sendSmsCode($templateid, $mobile, $deviceId = '', $authCode)
    {
        $url = 'https://api.netease.im/sms/sendcode.action';
        $data = array(
            'templateid' => $templateid,
            'mobile' => $mobile,
            'deviceId' => $deviceId,
            'authCode' => $authCode
        );

        $result = self::postDataCurl($url, $data);

        return $result;
    }


    /**
     * 发送模板短信
     * @param  $templateid [模板编号(由客服配置之后告知开发者)]
     * @param  $mobiles [验证码]
     * @param  $params [短信参数列表，用于依次填充模板，JSONArray格式，如["xxx","yyy"];对于不包含变量的模板，不填此参数表示模板即短信全文内容]
     * @return $result      [返回array数组对象]
     */
    public function sendSMSTemplate($templateid, $mobiles = array(), $params = '')
    {
        $url = 'https://api.netease.im/sms/sendtemplate.action';
        $data = array(
            'templateid' => $templateid,
            'mobiles' => json_encode($mobiles),
            'params' => json_encode($params)
        );
        if ($this->RequestType == 'curl') {
            $result = $this->postDataCurl($url, $data);
        } else {
            $result = $this->postDataFsockopen($url, $data);
        }
        return $result;
    }


    /**
     * @param $mobile string 手机号码
     * @param $type string 1注册 2找回密码 3验证旧手机 4.绑定新手机 5.设置二级密码 6.绑定银行卡 7.找回qq账号 8.注销账号 9.快捷认证(支付宝身份验证) 10.人工认证
     * @param array $param 参数
     * @param int $limit 单位:分钟
     * @return string
     * @throws BasicException
     * @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
     * @throws \Overtrue\EasySms\Exceptions\NoGatewayAvailableException
     */
    public static function send($mobile, $type, $param = [], $limit = 1) {
        $key = 'sms:' . $mobile . ':' . $type;
        if ($limit > 0 && app('redis')->get($key)) {
            throw new BasicException(0, $limit . '分钟可获取一次');
        }
        $sms_code = isset($param['code'])?$param['code']:mt_rand(1000, 9999);
        $templateId = self::getTemplateCode($type);
        if (env('SMS_REAL_SEND')) {
            try {
                $res = self::sendSmsCode($templateId, $mobile, '', $sms_code);
                if ($res['code'] != 200) {
                    Log::error("阿里云短信发送失败,失败信息：".$res['msg']);
                    return false;
                }
            } catch (Exception $exception) {
                $error_msg = $exception->getMessage() ?? '发送失败，请重试';
                Log::error($error_msg);
                throw new BasicException(0, $error_msg);
            }
        }

        app('model_sms_history')->create([
            'mobile'   => $mobile,
            'code'     => $sms_code,
            'add_time' => Carbon::now()->timestamp,
            'type'     => $type
        ]);

        app('redis')->setex($key, $limit*60, $sms_code);
        return true;
    }

    public static function getTemplateCode($type)
    {
        $config = self::getConfig();
        return $config['templateCode' . $type] ?? '';
    }

    public static function getConfig()
    {

        return config('sms.netease') ?? [];
    }

    public static function verify($mobile, $code, $type)
    {
        $where = [
            'mobile' => $mobile,
            'type'   => $type,
            'status' => 0,
        ];
        $data = app('model_sms_history')->where($where)->orderBy('id', 'desc')->first();
        $result = false;
        $key = 'u:login_error_times:' . $mobile . ':' . $type;

        if (isset($data) && $data->status == 0 && $data->code == $code) {
            if ($data->add_time < (Carbon::now()->timestamp - 1800)) {
                $status = 2;
                $result = false;
                self::$_error_msg = '验证码已过期';
            } else {
                $status = 1;
                $result = true;
            }
            app('model_sms_history')->where(['id' => $data->id, 'status' => 0])->update([
                'status'      => $status,
                'update_time' => Carbon::now()->timestamp,
            ]);
        }
        //记录错误次数
        if (!$result) {
            self::$_error_msg = '验证码错误';
            $times = app('redis')->incr($key);
            if ($times >= 5) {
                app('model_sms_history')->where($where)->update([
                    'status'      => 2,
                    'update_time' => Carbon::now()->timestamp,
                ]);
                self::$_error_msg = '验证码输入错误次数过多，已失效，请重新获取';
                app('redis')->del($key);

                return false;
            } elseif ($times == 1) {
                app('redis')->expire($key, strtotime(date('Ymd')) + 86400 - time());
            }
        }

        return $result;
    }
}
